﻿#include "BufferedInputStream.h"
#include "lang/Exception.h"
using namespace yzrilyzr_array;
using namespace yzrilyzr_lang;

namespace yzrilyzr_io{
	BufferedInputStream::~BufferedInputStream(){
		delete buf;
	}
	BufferedInputStream::BufferedInputStream(InputStream & s) :BufferedInputStream(s, 8192){}
	BufferedInputStream::BufferedInputStream(InputStream & s, u_index size) :FilterInputStream(s){
		buf=new ByteArray(size);
	}
	u_index BufferedInputStream::read1(ByteArray & b, u_index off, u_index len){
		int avail=count - pos;
		if(avail <= 0){
			/* If the requested length is at least as large as the buffer, and
			   if there is no mark/reset activity, do not bother to copy the
			   bytes into the local buffer.  In this way buffered streams will
			   cascade harmlessly. */
			if(len >= getBufIfOpen()->length && markpos < 0){
				return getInIfOpen()->read(b, off, len);
			}
			fill();
			avail=count - pos;
			if(avail <= 0) return -1;
		}
		int cnt=(avail < len)?avail:len;
		memcpy(b._array + off, getBufIfOpen()->_array + pos, cnt);
		pos+=cnt;
		return cnt;
	}
	int32_t BufferedInputStream::read(){
		if(pos >= count){
			fill();
			if(pos >= count)
				return -1;
		}
		return (*getBufIfOpen())[pos++] & 0xff;
	}
	ByteArray * BufferedInputStream::getBufIfOpen(){
		ByteArray * buffer=buf;
		if(buffer == nullptr)
			throw IOException("Stream closed");
		return buffer;
	}
	u_index BufferedInputStream::read(ByteArray & b){
		return read(b, 0, b.length);
	}
	u_index BufferedInputStream::read(ByteArray & b, u_index off, u_index len){
		getBufIfOpen(); // Check for closed stream
		if((off | len | (off + len) | (b.length - (off + len))) < 0){
			throw IndexOutOfBoundsException();
		} else if(len == 0){
			return 0;
		}
		u_index n=0;
		for(;;){
			u_index nread=read1(b, off + n, len - n);
			if(nread <= 0)
				return (n == 0)?nread:n;
			n+=nread;
			if(static_cast<u_index>(n) >= len)
				return n;
			// if not closed but no bytes available, return
			if(input != nullptr && input->available() <= 0)
				return n;
		}
	}
	u_index BufferedInputStream::available(){
		u_index n=count - pos;
		u_index avail=getInIfOpen()->available();
		uint64_t max=0x7fffffffffffffffL;
		return n > (max - avail)?max:n + avail;
	}
	void BufferedInputStream::close(){
		input->close();
	}
	void BufferedInputStream::fill(){
		ByteArray * buffer=getBufIfOpen();
		if(markpos < 0)
			pos=0;            /* no mark: throw away the buffer */
		else if(pos >= buffer->length){ /* no room left in buffer */
			if(markpos > 0){  /* can throw away early part of the buffer */
				u_index sz=pos - markpos;
				memcpy(buffer->_array, buffer->_array + markpos, sz);
				pos=sz;
				markpos=0;
			} else if(buffer->length >= marklimit){
				markpos=-1;   /* buffer got too big, invalidate mark */
				pos=0;        /* drop buffer contents */
			} else{            /* grow buffer */
				u_index nsz=pos * 2;
				if(nsz > marklimit)
					nsz=marklimit;
				ByteArray * nbuf=new ByteArray(nsz);
				memcpy(nbuf->_array, buffer->_array, pos);
				buffer=nbuf;
			}
		}
		count=pos;
		u_index n=getInIfOpen()->read(*buffer, pos, buffer->length - pos);
		if(n > 0)
			count=n + pos;
	}
	InputStream * BufferedInputStream::getInIfOpen(){
		if(input == nullptr)
			throw IOException("Stream closed");
		return input;
	}
}